import Cesium from '../../Ces/Cesium'
import FlyingTail from './FlyingTail';
import FlyingPath from './FlyingPath';
import FlyingFlow from './FlyingFlow';

let modelMatrixScratch;

let quaternionScratch;

function FlyingPaneEx(options) {
  this._roundTrip = options.roundTrip?? false
  const height = options.height??3000
  const positions = FlyingPaneEx.getArcPositions(options.positions, height, options.interpolationAlgorithm);

  this._posInPercent = 0.0;
  this._speed = options.speed?? 10
  this._positions = positions;
  this.createModel(options);

  this._showPath = options.showPath?? false
  if (this._showPath) {
    this._path = new FlyingPath({
      ...options,
      positions,
      stationPosition: options.positions,
    });
  }

  this._showTail = options.showTail?? false
  if (this._showTail) {
    this._tail = new FlyingTail({ positions });
  }

  this._showFlow = options.showFlow?? false
  if (this._showFlow) {
    this._flow = new FlyingFlow({
      ...options,
      positions,
    });
  }
  this._dirty = false;
}

FlyingPaneEx.sample = function (positions, interpolationAlgorithm) {
  const sampledPositionProperty = new Cesium.SampledPositionProperty();

  if (Cesium.defined(interpolationAlgorithm)) {
    sampledPositionProperty.setInterpolationOptions({
      interpolationAlgorithm,
      interpolationDegree: 4,
    });
  }

  const startTime = new Cesium.JulianDate();

  let endTime;

  for (let i = 0; i < positions.length; i++) {
    const t = Cesium.JulianDate.addSeconds(startTime, 3600 * 2 * i, new Cesium.JulianDate());

    sampledPositionProperty.addSample(t, positions[i]);
    endTime = t;
  }

  let positionsX = [];

  positionsX = Cesium.PathVisualizer._subSample(
    sampledPositionProperty,
    startTime,
    endTime,
    undefined,
    Cesium.ReferenceFrame.FIXED,
    600,
    positionsX.slice(),
  );

  return positionsX;
};

FlyingPaneEx.getArcPositions = function (positions, height, interpolationAlgorithm) {
  const positionsX = FlyingPaneEx.sample(positions, interpolationAlgorithm);

  const granularity = Cesium.Math.RADIANS_PER_DEGREE * 0.01;


  return Cesium.PolylinePipeline.generateCartesianRhumbArc({
    positions: positionsX,
    height,
    granularity,
  });
};

Object.defineProperties(FlyingPaneEx.prototype, {
  model: {
    get() {
      return this._model;
    },
  },
  path: {
    get() {
      return this._path;
    },
  },
  tail: {
    get() {
      return this._tal;
    },
  },
  flow: {
    get() {
      return this._flow;
    },
  },
  speed: {
    get() {
      return this._speed;
    },
    set(value) {
      if (this._speed !== value) {
        this._speed = value;
        this._dirty = true;
      }
    },
  },
  posInPercent: {
    get() {
      return this._posInPercent;
    },
    set(value) {
      if (this._posInPercent !== value) {
        this._posInPercent = value;
        this._dirty = true;
      }
    },
  },
});

function createPositionSampler(positions, speed, beginTime, currentPos, roundTrip) {
  const property = new Cesium.SampledPositionProperty();

  let fullDistance = 0;
  const posSecconds = [];

  for (let i = 0; i < positions.length; i++) {
    const curPos = positions[i];

    if (i === 0) {
      posSecconds.push(0);
    } else {
      const nextPos = positions[i - 1];
      const distance = Cesium.Cartesian3.distance(nextPos, curPos);

      fullDistance += distance;
      posSecconds.push(distance / speed);
    }
  }

  const fullTimeSecconds = fullDistance / speed;

  const currentPosTimeSeccond = fullTimeSecconds * currentPos;

  const takeOffTime = Cesium.JulianDate.addSeconds(beginTime, -currentPosTimeSeccond, new Cesium.JulianDate());
  const descendTime = Cesium.JulianDate.addSeconds(takeOffTime, fullTimeSecconds, new Cesium.JulianDate());

  const dir = Cesium.Cartesian3.divideComponents(positions[1], positions[0], new Cesium.Cartesian3());

  Cesium.Cartesian3.normalize(dir, dir);
  Cesium.Cartesian3.multiplyByScalar(dir, 0.001, dir);

  const posAtTimes = [];

  let posSeccond = 0;

  for (let i = 0; i < positions.length; i++) {
    const curPos = positions[i];

    posSeccond += posSecconds[i];
    posAtTimes.push(posSeccond / fullTimeSecconds);
    const posTime = Cesium.JulianDate.addSeconds(takeOffTime, posSeccond, new Cesium.JulianDate());

    property.addSample(posTime, curPos);
  }

  property.midTime = Cesium.JulianDate.addSeconds(takeOffTime, fullTimeSecconds * 0.5, new Cesium.JulianDate());
  property.takeOffTime = takeOffTime;
  property.descendTime = descendTime;
  property.posAtTimes = posAtTimes;
  property.fullTimeSecconds = fullTimeSecconds;
  property.fullDistance = fullDistance;

  return property;
}

FlyingPaneEx.prototype.updatePositionProperty = function (startTime1) {
  const positions = this._positions;
  const speed = this.speed;
  const currentPos = this._posInPercent;

  const startTime = Cesium.JulianDate.clone(startTime1, this._startTime);

  const positionsX = [];

  for (let i = 0; i < positions.length; i++) {
    positionsX.push(positions[i]);
  }

  if (this._roundTrip) {
    for (let i = 1; i < positions.length; i++) {
      positionsX.push(positions[positions.length - 1 - i]);
    }
  }

  const positionProperty = createPositionSampler(
    positionsX,
    speed,
    startTime,
    currentPos,
  );

  positionProperty.backwardExtrapolationType = Cesium.ExtrapolationType.HOLD;
  positionProperty.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD;

  this._totolSecconds = positionProperty.fullTimeSecconds;
  this._velocityOrientationProperty = new Cesium.VelocityOrientationProperty(positionProperty);
  this._positionProperty = positionProperty;
};

FlyingPaneEx.prototype.createModel = function (options) {
  this.updatePositionProperty(options.startTime);

  const that = this;

  const promise = Cesium.Model.fromGltfAsync({
    url: options.url,
    show: true,
    asynchronous: false,
    minimumPixelSize: options.minimumPixelSize?? 96,
    color: options.color?? new Cesium.Color(1.0, 1.0, 1.0, 1.0),
    silhouetteSize: options.silhouetteSize?? 1,
    silhouetteColor: options.silhouetteColor?? Cesium.Color.RED,
    shadows: Cesium.ShadowMode.DISABLED,
  });

  promise.then((model) => {
    model.readyEvent.addEventListener(() => {
      if (Cesium.defined(model.activeAnimations)) {
        model.activeAnimations.addAll({
          loop: Cesium.ModelAnimationLoop.REPEAT, // Loop the animation
        });
      }
    });
    that._model = model;
  }).catch((error) => {
    console.error(error);
  });

  this._rotationScratch = new Cesium.Matrix3();
  this._position = new Cesium.Cartesian3();
  if (!Cesium.defined(modelMatrixScratch)) {
    modelMatrixScratch = new Cesium.Matrix4();
  }

  if (!Cesium.defined(quaternionScratch)) {
    quaternionScratch = new Cesium.Quaternion();
  }
};

FlyingPaneEx.prototype.destroy = function () {
  this._model = this._model && this._model.destroy();
  this._tail = this._tail && this._tail.destroy();
  this._path = this._path && this._path.destroy();

  return Cesium.destroyObject(this);
};

FlyingPaneEx.prototype.isDestroyed = function () {
  return false;
};

FlyingPaneEx.prototype.getModelMatrix = function (time) {
  const positionProperty = this._positionProperty;
  const velocityOrientationProperty = this._velocityOrientationProperty;
  const position = positionProperty.getValue(time, this._position);

  if (Cesium.defined(position)) {
    const quaternion = velocityOrientationProperty.getValue(time, quaternionScratch);

    if (Cesium.defined(quaternion)) {
      Cesium.Matrix3.fromQuaternion(quaternion, this._rotationScratch);
    }

    return Cesium.Matrix4.fromRotationTranslation(this._rotationScratch, position, modelMatrixScratch);
  }

  return undefined;
};

FlyingPaneEx.prototype.update = function (frameState) {
  if (!Cesium.defined(this._model)) {
    return;
  }

  if (Cesium.defined(this._path)) {
    this._path.update(frameState);
  }

  if (frameState.passes.render) {
    if (this._dirty) {
      this._dirty = false;
      this.updatePositionProperty(frameState.time);
    }

    const positionProperty = this._positionProperty;
    const fullTimeSecconds = positionProperty.fullTimeSecconds;
    const takeOffTime = positionProperty.takeOffTime;
    const posInPercent = Cesium.JulianDate.secondsDifference(frameState.time, takeOffTime) / fullTimeSecconds;

    this._posInPercent = Cesium.Math.clamp(posInPercent, 0.0, 1.0);
    if (Cesium.defined(this._flow)) {
      if (this._roundTrip) {
        this._flow.reverse = Cesium.JulianDate.greaterThan(frameState.time, positionProperty.midTime);
      }
      this._flow.update(frameState);
    }
    if (Cesium.defined(this._tail)) {
      this._tail.showPercent = this._posInPercent * 100;
      this._tail.update(frameState);
    }
    const modelMatrix = this.getModelMatrix(frameState.time);

    if (Cesium.defined(modelMatrix)) {
      const model = this._model;

      model.modelMatrix = modelMatrix;
      model.update(frameState);
    }
  }
};

export default FlyingPaneEx;
